/*
 * Copyright (c) 2024 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#include <arch/gic_v2.h>
#include <stdlib/assert.h>
#include <core/dprintf.h>
#include <tools/macros.h>
#include <arch/cpu.h>

#define REG32(addr) (*((volatile uint32_t *)(addr)))
#define REG16(addr) (*((volatile uint16_t *)(addr)))

typedef struct {
    uint64_t dist_base;
    uint64_t cpu_base;
    uint64_t hyper_base;
} gicv2_t;

static gicv2_t gic;

/*******************************************************************************
 * CPU interface register
 ******************************************************************************/
#define GICC_CTLR(base)   REG32((base) + 0x0U)
#define GICC_PMR(base)    REG32((base) + 0x4U)
#define GICC_BPR(base)    REG32((base) + 0x8U)
#define GICC_IAR(base)    REG32((base) + 0xcU)
#define GICC_EOIR(base)   REG32((base) + 0x10U)
#define GICC_RPR(base)    REG32((base) + 0x14U)
#define GICC_HPPIR(base)  REG32((base) + 0x18U)
#define GICC_ABPR(base)   REG32((base) + 0x1cU)
#define GICC_AIAR(base)   REG32((base) + 0x20U)
#define GICC_AEOIR(base)  REG32((base) + 0x24U)
#define GICC_AHPPIR(base) REG32((base) + 0x28U)
#define GICC_APR(base)    REG32((base) + 0xdcU)
#define GICC_NSAPR(base)  REG32((base) + 0xe0U)
#define GICC_IIDR(base)   REG32((base) + 0xfcU)
#define GICC_DIR(base)    REG32((base) + 0x1000U)

/*******************************************************************************
 * Distributor register
 ******************************************************************************/
#define GICD_CTLR(base)          REG32((base) + 0x0U)
#define GICD_TYPER(base)         REG32((base) + 0x4U)
#define GICD_IIDR(base)          REG32((base) + 0x8U)
#define GICD_IGROUPR(base, n)    REG32((base) + 0x80U + ((n) / 32U) * 4U)
#define GICD_ISENABLER(base, n)  REG32((base) + 0x100U + ((n) / 32U) * 4U)
#define GICD_ICENABLER(base, n)  REG32((base) + 0x180U + ((n) / 32U) * 4U)
#define GICD_ISPENDR(base, n)    REG32((base) + 0x200U + ((n) / 32U) * 4U)
#define GICD_ICPENDR(base, n)    REG32((base) + 0x280U + ((n) / 32U) * 4U)
#define GICD_ISACTIVER(base, n)  REG32((base) + 0x300U + ((n) / 32U) * 4U)
#define GICD_ICACTIVER(base, n)  REG32((base) + 0x380U + ((n) / 32U) * 4U)
#define GICD_IPRIORITYR(base, n) REG32((base) + 0x400U + ((n) / 4U) * 4U)
#define GICD_ITARGETSR(base, n)  REG32((base) + 0x800U + ((n) / 4U) * 4U)
#define GICD_ICFGR(base, n)      REG32((base) + 0xc00U + ((n) / 16U) * 4U)
#define GICD_NSACR(base, n)      REG32((base) + 0xe00U + ((n) / 16U) * 4U)
#define GICD_SGIR(base, n)       REG32((base) + 0xf00U)
#define GICD_CPENDSGIR(base, n)  REG32((base) + 0xf10U + ((n) / 4U) * 4U)
#define GICD_SPENDSGIR(base, n)  REG32((base) + 0xf20U + ((n) / 4U) * 4U)


void gic_dist_disable(void)
{
    GICD_CTLR(gic.dist_base) = 0x0U;
}

void gic_dist_enable(void)
{
    GICD_CTLR(gic.dist_base) = GICD_CTLR_ENABLE_BIT;
}

uint32_t gic_get_irqnum(void)
{
    return ((GICD_TYPER(gic.dist_base) & 0x1fU) + 1U) * 32U;
}

uint32_t gic_read_typer(void)
{
    return GICD_TYPER(gic.dist_base);
}

uint32_t gic_read_iidr(void)
{
    return GICD_IIDR(gic.dist_base);
}

void gic_set_igroupr(uint32_t irq)
{
    uint32_t reg_val = GICD_IGROUPR(gic.dist_base, irq);
    uint32_t bit_num = irq % 32U;

    reg_val &= (~(1U << bit_num));
    GICD_IGROUPR(gic.dist_base, irq) = reg_val | (uint32_t)(1U << bit_num);
}

uint32_t gic_get_igroupr(uint32_t irq)
{
    return (GICD_IGROUPR(gic.dist_base, irq) >> (irq % 32U)) & 0x1U;
}

void gic_enable_irq(uint32_t irq)
{
    GICD_ISENABLER(gic.dist_base, irq) = (1U << (irq % 32U));
}

void gic_disable_irq(uint32_t irq)
{
    GICD_ICENABLER(gic.dist_base, irq) = (1U << (irq % 32U));
}

void gic_clear_pend(uint32_t irq)
{
    if (irq < 16U) {
        GICD_CPENDSGIR(gic.dist_base, irq) = (1U << ((irq % 4U) * 8U));
    } else {
        GICD_ICPENDR(gic.dist_base, irq) = (1U << (irq % 32U));
    }
}

void gic_set_pend(uint32_t irq)
{
    if (irq < 16U) {
        GICD_SPENDSGIR(gic.dist_base, irq) = (1U << ((irq % 4U) * 8U));
    } else {
        GICD_ISPENDR(gic.dist_base, irq) = (1U << (irq % 32U));
    }
}

void gic_set_active(uint32_t irq)
{
    GICD_ISACTIVER(gic.dist_base, irq) = (1U << (irq % 32U));
}

void gic_clear_active(uint32_t irq)
{
    GICD_ICACTIVER(gic.dist_base, irq) = (1U << irq % 32U);
}

void gic_set_priority(uint32_t irq, uint32_t pri)
{
    uint32_t reg_val = GICD_IPRIORITYR(gic.dist_base, irq);

    reg_val &= ~(0xFFUL << ((irq % 4U) * 8U));
    reg_val |= (pri & 0xFFU) << ((irq % 4U) * 8U);

    GICD_IPRIORITYR(gic.dist_base, irq) = reg_val;
}

uint32_t gic_get_priority(uint64_t irq)
{
    return (GICD_IPRIORITYR(gic.dist_base, irq) >> ((irq % 4U) * 8U)) & 0xFFU;
}

uint32_t gic_get_targets(uint32_t irq)
{
    return (GICD_ITARGETSR(gic.dist_base, irq) >> ((irq % 4U) * 8U)) & 0xFFU;
}

void gic_set_targets(uint32_t irq, uint32_t target)
{
    uint32_t reg_val = GICD_ITARGETSR(gic.dist_base, irq);

    reg_val &= ~(0xFFUL << ((irq % 4U) * 8U));
    reg_val |= (target & 0xFFU) << ((irq % 4U) * 8U);

    GICD_ITARGETSR(gic.dist_base, irq) = reg_val;
}

void gic_set_cfg(uint32_t irq, uint32_t cfg)
{
    uint32_t reg_val = GICD_ICFGR(gic.dist_base, irq);

    reg_val &= ~(3U << ((irq % 16U) * 2U));
    reg_val |= ((cfg & 0x3U) << (((irq % 16U) * 2U) + 1U));

    GICD_ICFGR(gic.dist_base, irq) = reg_val;
}

uint32_t gic_get_cfg(uint32_t irq)
{
    return GICD_ICFGR(gic.dist_base, irq) >> ((irq % 16U) >> 1U);
}

void gic_send_sgi(uint32_t irq, uint32_t filter, uint8_t cpulist)
{
    uint32_t reg_val = 0;

    assert(irq < 16);
    reg_val = (filter & 0x3U) << GICD_SGIR_TRGLSTFLT_OFF;       // TargetListFilter
    reg_val |= (cpulist & 0xFFU) << GICD_SGIR_CPUTRGLST_OFF;    // CPUTargetList
    reg_val |= (irq & 0xFU);                                    // SGI ID

    GICD_SGIR(gic.dist_base, irq) = reg_val;
}

uint32_t gic_get_active_irq(void)
{
    return GICC_IAR(gic.cpu_base) & lowbitsmask(GICC_IAR_ID_LEN);
}

void gic_set_eoi(uint32_t irq)
{
    uint32_t val = 0U;

    if (irq < 16U) {
        uint32_t cpu = current_cpu();
        val = cpu << GICC_EOIR_CPU_OFF | irq;
    } else {
        val = (irq & lowbitsmask(GICC_EOIR_ID_LEN));
    }

    GICC_EOIR(gic.cpu_base) = val;
}

void gic_set_dir(uint32_t irq)
{
    int32_t val = 0U;

    if (irq < 16U) {
        uint32_t cpu = current_cpu();
        val = cpu << GICC_DIR_CPU_OFF | irq;
    } else {
        val = irq;
    }

    GICC_DIR(gic.cpu_base) = val;
}

void gic_dist_init(uint64_t base)
{
    uint32_t i       = 0U;
    uint32_t max_irq = 0;

    dprintf( "GICD Init Start");
    gic.dist_base = base;

    max_irq = gic_get_irqnum();
    dprintf( "gic max irq number %d", max_irq);

    if (max_irq > CONFIG_IRQ_COUNT) {
        max_irq = CONFIG_IRQ_COUNT;
    }

    gic_dist_disable();

    /* interrupt is level-sensitive */
    for (i = 16U; i < max_irq; i += 16U) {
        GICD_ICFGR(base, i) = 0U;
    }

    /* Set target cpus */
    for (i = 32U; i < max_irq; i += 4U) {
        GICD_ITARGETSR(base, i) = 0x01010101U;
    }

    /* disable all interrupts and clear pending */
    for (i = 0U; i < max_irq; i += 32U) {
        GICD_ICENABLER(base, i) = 0xffffffffU;
        GICD_ICPENDR(base, i)   = 0xffffffffU;
    }

    /* set intertupt priority */
    for (i = 0U; i < max_irq; i += 4U) {
        if (i < 16) {
            GICD_IPRIORITYR(base, i) = 0x10101010U;    // priority 16
        } else {
            GICD_IPRIORITYR(base, i) = 0x20202020U;    // priority 32
        }
    }

    gic_dist_enable();
    dprintf( "GICD Init End");
}

void gic_cpu_init(uint64_t base)
{
    dprintf( "GICC Init Start!");
    gic.cpu_base = base;

    /* program the idle priority in the PMR */
    GICC_PMR(base) = 0xffU;

    /* Enable CPU interface */
    GICC_CTLR(base) = GICC_CTLR_ENABLEGRP1_BIT;

    dprintf( "GICC Init End!");
}

